package com.cory.boot.config;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import com.beust.jcommander.internal.Maps;
import com.cory.boot.utils.SpringUtils;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleDate;
import freemarker.template.SimpleHash;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.Version;

@Configuration
@DependsOn("springUtils")
public class FreemarkerConfig {

    @Autowired
    protected FreeMarkerViewResolver freeMarkerViewResolver;
    
    @Autowired
    private FreeMarkerConfigurer freeMarkerConfigurer;

    @Autowired
    protected FreemarkerStaticModels freemarkerStaticModels;

    @PostConstruct
    public void init() {
        freeMarkerViewResolver.setAttributesMap(freemarkerStaticModels);
        
        ObjectWrapper objectWrapper = new CustomObjectWrapper(freemarker.template.Configuration.getVersion());
        freeMarkerConfigurer.getConfiguration().setObjectWrapper(objectWrapper);
        
        Collection<TemplateModel> beans = SpringUtils.getApplicationContext().getBeansOfType(TemplateModel.class).values();
        Map<String, SimpleHash> prefixMap = Maps.newHashMap();
        beans.forEach(bean -> {
            FreemarketTag freemarketTag = AnnotationUtils.findAnnotation(bean.getClass(), FreemarketTag.class);
            if (freemarketTag != null) {
                if (StringUtils.isNotBlank(freemarketTag.prefix())) {
                    if (!prefixMap.containsKey(freemarketTag.prefix())) {
                        prefixMap.put(freemarketTag.prefix(), new SimpleHash(objectWrapper));
                    }
                    prefixMap.get(freemarketTag.prefix()).put(freemarketTag.name(), bean);
                } else {
                    freeMarkerConfigurer.getConfiguration().setSharedVariable(freemarketTag.name(), bean);
                }
            }
        });
        prefixMap.entrySet().forEach(i -> {
            freeMarkerConfigurer.getConfiguration().setSharedVariable(i.getKey(), i.getValue());
        });
    }
    
    public void setStaticModels(String key, String value) {
        freeMarkerViewResolver.getAttributesMap().put(key, freemarkerStaticModels.useStaticPackage(value));
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface FreemarketTag {
        String name();
        String prefix() default "";
    }

    /**
     * 支持再页面直接使用java静态方法
     * 
     * @author xiongcong
     * @date 2019-04-28
     */
    @Configuration
    static class FreemarkerStaticModels extends HashMap<String, Object> {
        private static final long serialVersionUID = 1L;

        @Value("classpath:/config/freemarkerStaticClass.properties")
        private Resource resource;

        private Properties staticModels;

        @PostConstruct
        public void init() {
            staticModels = new Properties();
            try {
                staticModels.load(resource.getInputStream());
            } catch (IOException ex) {

            }
            staticModels.putIfAbsent("CommonData", "com.cory.boot.utils.CommonData");
            staticModels.putIfAbsent("Uploader", "com.cory.boot.component.uploader.UploaderUtils");
            
            setStaticModels(staticModels);
        }

        public void setStaticModels(String key, String value) {
            this.put(key, useStaticPackage(value));
        }

        public void setStaticModels(Properties staticModels) {
            if (staticModels != null) {
                Set<String> keys = staticModels.stringPropertyNames();
                for (String key : keys) {
                    this.put(key, useStaticPackage(staticModels.getProperty(key)));
                }
            }
        }

        public TemplateHashModel useStaticPackage(String packageName) {
            try {
                BeansWrapper wrapper = new BeansWrapperBuilder(freemarker.template.Configuration.getVersion()).build();
                TemplateHashModel staticModels = wrapper.getStaticModels();
                return (TemplateHashModel) staticModels.get(packageName);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }


    private static class CustomObjectWrapper extends DefaultObjectWrapper {

        public CustomObjectWrapper(Version incompatibleImprovements) {
            super(incompatibleImprovements);
        }

        @Override
        public TemplateModel handleUnknownType(Object obj) throws TemplateModelException {
            if (obj instanceof LocalDateTime) {
                Timestamp timestamp = Timestamp.valueOf((LocalDateTime) obj);
                return new SimpleDate(timestamp);
            }
            if (obj instanceof LocalDate) {
                Date date = Date.valueOf((LocalDate) obj);
                return new SimpleDate(date);
            }
            if (obj instanceof LocalTime) {
                Time time = Time.valueOf((LocalTime) obj);
                return new SimpleDate(time);
            }
            return super.handleUnknownType(obj);
        }
    }
}
